package arg.marshon.publiclibrary.autoviewpager;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.Interpolator;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
// TODO add attr support?
public class AutoScrollViewPager extends ViewPager implements ViewPager.PageTransformer {
public interface OnPageClickListener {
void onPageClick(AutoScrollViewPager pager, int position);
}
private static final int MSG_AUTO_SCROLL = 0;
private static final int DEFAULT_INTERNAL_IM_MILLIS = 2000;
private PagerAdapter wrappedPagerAdapter;
private PagerAdapter wrapperPagerAdapter;
private InnerOnPageChangeListener listener;
private AutoScrollFactorScroller scroller;
private H handler;
private boolean autoScroll = false;
private int intervalInMillis;
private float mInitialMotionX;
private float mInitialMotionY;
private float mLastMotionX;
private float mLastMotionY;
private int touchSlop;
private OnPageClickListener onPageClickListener;
private static class H extends Handler {
private AutoScrollViewPager autoScrollViewPager;
private WeakReference<AutoScrollViewPager> viewHolder;
public H(AutoScrollViewPager autoScrollViewPager){
this.autoScrollViewPager = autoScrollViewPager;
viewHolder = new WeakReference<AutoScrollViewPager>(autoScrollViewPager);
}
@Override
public void handleMessage(Message msg) {
if (viewHolder.get()!=null){
switch (msg.what) {
case MSG_AUTO_SCROLL:
autoScrollViewPager.setCurrentItem(autoScrollViewPager.getCurrentItem() + 1);
sendEmptyMessageDelayed(MSG_AUTO_SCROLL, autoScrollViewPager.intervalInMillis);
break;
default:
super.handleMessage(msg);
break;
}
}
}
}
public AutoScrollViewPager(Context context) {
super(context);
init();
}
public AutoScrollViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
listener = new InnerOnPageChangeListener();
super.setOnPageChangeListener(listener);
handler = new H(this);
touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
setPageTransformer(true,this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
handler.removeMessages(MSG_AUTO_SCROLL);
}
public void startAutoScroll() {
startAutoScroll(intervalInMillis != 0 ? intervalInMillis : DEFAULT_INTERNAL_IM_MILLIS);
}
public void startAutoScroll(int intervalInMillis) {
// Only post scroll message when necessary.
if (getCount() > 1) {
this.intervalInMillis = intervalInMillis;
autoScroll = true;
handler.removeMessages(MSG_AUTO_SCROLL);
handler.sendEmptyMessageDelayed(MSG_AUTO_SCROLL, intervalInMillis);
}
}
public void stopAutoScroll() {
autoScroll = false;
handler.removeMessages(MSG_AUTO_SCROLL);
}
public void setInterval(int intervalInMillis) {
this.intervalInMillis = intervalInMillis;
}
public void setScrollFactgor(double factor) {
setScrollerIfNeeded();
scroller.setFactor(factor);
}
@Override
public void setOnPageChangeListener(OnPageChangeListener listener) {
this.listener.setOnPageChangeListener(listener);
}
@Override
public void setAdapter(PagerAdapter adapter) {
wrappedPagerAdapter = adapter;
wrapperPagerAdapter = (wrappedPagerAdapter == null) ? null : new AutoScrollPagerAdapter(adapter);
super.setAdapter(wrapperPagerAdapter);
if (adapter != null && adapter.getCount() != 0) {
post(new Runnable() {
@Override
public void run() {
setCurrentItem(0, false);
}
});
}
}
@Override
public PagerAdapter getAdapter() {
// In order to be compatible with ViewPagerIndicator
return wrappedPagerAdapter;
}
@Override
public void setCurrentItem(int item) {
super.setCurrentItem(item + 1);
}
@Override
public void setCurrentItem(int item, boolean smoothScroll) {
super.setCurrentItem(item + 1, smoothScroll);
}
@Override
public int getCurrentItem() {
int curr = super.getCurrentItem();
if (wrappedPagerAdapter != null && wrappedPagerAdapter.getCount() > 1) {
if (curr == 0) {
curr = wrappedPagerAdapter.getCount() - 1;
} else if (curr == wrapperPagerAdapter.getCount() - 1) {
curr = 0;
} else {
curr = curr - 1;
}
}
return curr;
}
public OnPageClickListener getOnPageClickListener() {
return onPageClickListener;
}
public void setOnPageClickListener(OnPageClickListener onPageClickListener) {
this.onPageClickListener = onPageClickListener;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
getParent().getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (MotionEventCompat.getActionMasked(ev)) {
case MotionEvent.ACTION_DOWN:
if (getCurrentItemOfWrapper() + 1 == getCountOfWrapper()) {
setCurrentItem(0, false);
} else if (getCurrentItemOfWrapper() == 0) {
setCurrentItem(getCount() - 1, false);
}
handler.removeMessages(MSG_AUTO_SCROLL);
mInitialMotionX = ev.getX();
mInitialMotionY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
mLastMotionX = ev.getX();
mLastMotionY = ev.getY();
if ((int) Math.abs(mLastMotionX - mInitialMotionX) > touchSlop || (int) Math.abs(mLastMotionY - mInitialMotionY) > touchSlop) {
mInitialMotionX = 0.0f;
mInitialMotionY = 0.0f;
}
break;
case MotionEvent.ACTION_UP:
if (autoScroll) {
startAutoScroll();
}
// Manually swipe not affected by scroll factor.
if (scroller != null) {
final double lastFactor = scroller.getFactor();
scroller.setFactor(1);
post(new Runnable() {
@Override
public void run() {
scroller.setFactor(lastFactor);
}
});
}
mLastMotionX = ev.getX();
mLastMotionY = ev.getY();
if ((int) mInitialMotionX != 0 && (int) mInitialMotionY != 0) {
if ((int) Math.abs(mLastMotionX - mInitialMotionX) < touchSlop
&& (int) Math.abs(mLastMotionY - mInitialMotionY) < touchSlop) {
mInitialMotionX = 0.0f;
mInitialMotionY = 0.0f;
mLastMotionX = 0.0f;
mLastMotionY = 0.0f;
if (onPageClickListener != null) {
onPageClickListener.onPageClick(this, getCurrentItem());
}
}
}
break;
}
return super.onTouchEvent(ev);
}
/**
* Get current item of the outer wrapper adapter.
*/
private int getCurrentItemOfWrapper() {
return super.getCurrentItem();
}
/**
* Get item count of the outer wrapper adapter.
*/
private int getCountOfWrapper() {
if (wrapperPagerAdapter != null) {
return wrapperPagerAdapter.getCount();
}
return 0;
}
/**
* Get item count of the adapter which is set by user
*/
private int getCount() {
if (wrappedPagerAdapter != null) {
return wrappedPagerAdapter.getCount();
}
return 0;
}
private void setScrollerIfNeeded() {
if (scroller != null) {
return;
}
try {
Field scrollerField = ViewPager.class.getDeclaredField("mScroller");
scrollerField.setAccessible(true);
Field interpolatorField = ViewPager.class.getDeclaredField("sInterpolator");
interpolatorField.setAccessible(true);
scroller = new AutoScrollFactorScroller(getContext(), (Interpolator) interpolatorField.get(null));
scrollerField.set(this, scroller);
} catch (Exception e) {
e.printStackTrace();
}
}
private class InnerOnPageChangeListener implements OnPageChangeListener {
private OnPageChangeListener listener;
private int lastSelectedPage = -1;
public InnerOnPageChangeListener() {
}
public InnerOnPageChangeListener(OnPageChangeListener listener) {
setOnPageChangeListener(listener);
}
public void setOnPageChangeListener(OnPageChangeListener listener) {
this.listener = listener;
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == SCROLL_STATE_IDLE && getCount() > 1) {
if (getCurrentItemOfWrapper() == 0) {
// scroll to the last page
setCurrentItem(getCount() - 1, false);
} else if (getCurrentItemOfWrapper() == getCountOfWrapper() - 1) {
// scroll to the first page
setCurrentItem(0, false);
}
}
if (listener != null) {
listener.onPageScrollStateChanged(state);
}
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (listener != null && position > 0 && position < getCount()) {
listener.onPageScrolled(position - 1, positionOffset, positionOffsetPixels);
}
}
@Override
public void onPageSelected(final int position) {
// if (listener != null && position != 0 && position < wrappedPagerAdapter.getCount() + 1) {
if (listener != null) {
final int pos;
// Fix position
if (position == 0) {
pos = getCount() - 1;
} else if (position == getCountOfWrapper() - 1) {
pos = 0;
} else {
pos = position - 1;
}
// Comment this, onPageSelected will be triggered twice for position 0 and getCount -1.
// Uncomment this, PageIndicator will have trouble.
// if (lastSelectedPage != pos) {
lastSelectedPage = pos;
// Post a Runnable in order to be compatible with ViewPagerIndicator because
// onPageSelected is invoked before onPageScrollStateChanged.
AutoScrollViewPager.this.post(new Runnable() {
@Override
public void run() {
listener.onPageSelected(pos);
}
});
// }
}
}
}
private static final float MIN_SCALE = 0.75f;
@Override
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);
} else if (position <= 0) { // [-1,0]
// Use the default slide transition when moving to the left page
view.setAlpha(1);
view.setTranslationX(0);
view.setScaleX(1);
view.setScaleY(1);
} else if (position <= 1) { // (0,1]
// Fade the page out.
view.setAlpha(1 - position);
// Counteract the default slide transition
view.setTranslationX(pageWidth * -position);
// Scale the page down (between MIN_SCALE and 1)
float scaleFactor = MIN_SCALE
+ (1 - MIN_SCALE) * (1 - Math.abs(position));
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}